package io.github.openfeign.spring.enhancer;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Opcode;
import lombok.extern.slf4j.Slf4j;

/**
 * 默认客户端字节码转换器 - HTTP连接处理增强
 *
 * <h3>核心改造：</h3>
 * <ul>
 *   <li>为HTTP连接建立过程增加延迟写入支持</li>
 *   <li>实现以下关键修改：
 *     <ol>
 *       <li>插入延迟输出检查逻辑</li>
 *       <li>重构请求体写入流程</li>
 *       <li>支持消费者模式流式输出</li>
 *     </ol>
 *   </li>
 * </ul>
 *
 * <h3>代码改造对比：</h3>
 *
 * <h4>原始实现（即时写入模式）:</h4>
 * <pre>{@code
 * if (request.body() != null) {
 *     // 直接写入字节数组
 *     out.write(request.body());
 * }
 * }</pre>
 *
 * <h4>优化后实现（延迟流式模式）:</h4>
 * <pre>{@code
 * if (hasDelayOutput()) {
 *     // 通过消费者模式延迟写入
 *     getDelayOutput().accept(out);
 *   return connection;
 * }
 * if (hasRequestBody()) {
 *     // 保持原有写入逻辑
 *     out.write(request.body());
 * }
 * }</pre>
 *
 * <h3>主要增强点：</h3>
 * <div class="enhancement">
 *   <h4>延迟输出接入点</h4>
 *   <ul>
 *     <li><strong>智能缓冲策略：</strong>
 *       <ul>
 *         <li>当检测到delayOutput时启用流式模式</li>
 *         <li>自动选择FixedLength或Chunked传输方式</li>
 *       </ul>
 *     </li>
 *     <li><strong>压缩处理优化：</strong>
 *       <ul>
 *         <li>保持GZIP/Deflate压缩逻辑</li>
 *         <li>支持流式压缩处理</li>
 *       </ul>
 *     </li>
 *   </ul>
 * </div>
 *
 *
 * @see feign.Client.Default 默认Feign客户端实现
 * @see java.util.zip.GZIPOutputStream GZIP压缩流
 */
@Slf4j
public class DefaultClientBytecodeTransformer extends AbstractBytecodeTransformer {

    private final long bufferSize;

    public DefaultClientBytecodeTransformer(final ClassPool classPool, final String classOutPath, final long bufferSize) {
        super(classPool, classOutPath);
        this.bufferSize = bufferSize;
    }

    /**
     * {@inheritDoc}
     */
    public void transformBytecode() throws NotFoundException, CannotCompileException, BadBytecode {
        if(log.isDebugEnabled()){
            log.debug("Start transforming feign.Client$Default");
        }
        final CtClass ctClass = classPool.get("feign.Client$Default");
        final CtMethod method = ctClass.getDeclaredMethod(
                "convertAndSend",
                new CtClass[]{classPool.get("feign.Request"), classPool.get("feign.Request$Options")}
        );
        // 找到位置后插入逻辑
        insertCodeAtTargetMethod(
                method,
                "feign.Request",
                "body",
                "     if ($1.requestTemplate().getDelayOutput() != null) {\n" +
                        "        if (disableRequestBuffering) {\n" +
                        "          if (contentLength != null) {\n" +
                        "            connection.setFixedLengthStreamingMode(contentLength.intValue());\n" +
                        "          } else {\n" +
                        "            connection.setChunkedStreamingMode(" + bufferSize + ");\n" +
                        "          }\n" +
                        "        }\n" +
                        "        connection.setDoOutput(true);\n" +
                        "        java.io.OutputStream out = connection.getOutputStream();\n" +
                        "        if (gzipEncodedRequest) {\n" +
                        "          out = new java.util.zip.GZIPOutputStream(out);\n" +
                        "        } else if (deflateEncodedRequest) {\n" +
                        "          out = new java.util.zip.DeflaterOutputStream(out);\n" +
                        "        }\n" +
                        "        try {\n" +
                        "          $1.requestTemplate().getDelayOutput().accept(out);\n" +
                        "        } finally {\n" +
                        "          try {\n" +
                        "            out.close();\n" +
                        "          } catch (java.io.IOException suppressed) { \n" +
                        "          }\n" +
                        "        }\n" +
                        "        return connection;\n" +
                        "      }\n",
                Opcode.INVOKEVIRTUAL,
                AbstractBytecodeTransformer::locateIfNullInstruction);
        redefineClassInMemory(ctClass);
        if(log.isDebugEnabled()){
            log.debug("Successfully transforming feign.Client$Default");
        }
    }
}
